#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <stdexcept>
#include <iomanip>
#include <algorithm>
#include <cstdio>
#include <memory>
#include <stdexcept>
#include <array>

#include "rocksdb/db.h"
#include "rocksdb/options.h"

using namespace rocksdb;
using namespace std;

string data_path = "./dataset/DataSet/Comments.xml";
string database_path = "./rocksdb-stack-overflow-comment";

#define     POS_ID_START    11

struct HumanReadable
{
    uintmax_t size{};

private:
    friend ostream &operator<<(ostream &os, HumanReadable hr)
    {
        int i{};
        double mantissa = hr.size;
        for (; mantissa >= 1024.; mantissa /= 1024., ++i)
        {
        }
        mantissa = ceil(mantissa * 10.) / 10.;
        os << mantissa << "BKMGTPE"[i];
        return i == 0 ? os : os << "B (" << hr.size << ')';
    }
};

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

int get_tot_lines(){
    string cmd = "cat " + data_path + " | wc -l";
    return stoi(exec(cmd.c_str()));
}

int main()
{
    DB *db;
    Options options;

    // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
    options.IncreaseParallelism();
    options.OptimizeLevelStyleCompaction();
    // create the DB if it's not already present
    options.create_if_missing = true;

    // open DB
    Status s = DB::Open(options, database_path, &db);
    assert(s.ok());

    cout << "please wait, this may takes a few minutes\n";

    struct HumanReadable tot_size;
    cout << "scaning file... please wait \n";
    int tot_records = get_tot_lines();
    int done_records = 0;
    cout << "this file have " << tot_records << " lines\n";

    ifstream inFile(data_path);
    string line;
    double complete = 0, pre_complete = 0;
    while (getline(inFile, line))
    {
        // every record has this pattern:
        //  <row Id="12345" .../>
        //01234567890
        // so we can find the Id through finding the right quotation after the left quotation

        // some other lines, like start and end of the Comment.xml is not started as "  <row"
        if(line.substr(0, 6) != "  <row"){
            cout << line << '\n';
            cout << "doesn't find <row" << "\n\n";
            continue;
        }

        // make sure <row follows the Id=
        if(line.substr(7, 3) != "Id="){
            cerr << line << '\n';
            cerr << "doesn't find Id=" << "\n\n";
            return -1;
        }

        // POS_ID_START=11, means the first number of the Id
        size_t pos_id_end = line.find('"', POS_ID_START);
        if(pos_id_end == string::npos){
            cerr <<  line << '\n';
            cerr << "can't find right quotation! \n\n";
            return -1;
        }

        string key = line.substr(POS_ID_START, pos_id_end - POS_ID_START);
        

        s = db->Put(WriteOptions(), key, line);
        if (!s.ok())
            cerr << s.ToString() << endl;
        else
        {
            ++done_records;
            complete = (double)done_records / tot_records;
            if(complete - pre_complete > 0.01){
                cout << setprecision(2) << complete * 100 << " % done\n";
                pre_complete = complete;
            }
            tot_size.size += key.size() + line.size();
        }
            
    }

    cout << tot_records << " records have been insert into rocks databse! " << endl;
    cout << "The altogether size of keys and values are " << tot_size << endl;
    cout << "rocks database is located in: " << database_path << endl;

    delete db;
}